package com.semanticweb.webapp.ontology;

/**
 * Class helps in creating SPARQL query for CIDOC ontology.
 * 
 * @author Sholy
 * 
 */
public class Query {

    public static final Integer UNKNOWN_QUERY = 0;
    public static final Integer SELECT_QUERY = 1;
    public static final Integer ASK_QUERY = 2;
    public static final Integer SELECT_LABEL_QUERY = 3;

    public static final String VAR_MARK = "?";

    private static final String VAR_NAME = "v";
    private static final String SELECT_LABEL = "selectLabel";

    protected StringBuffer query = new StringBuffer();
    protected Integer queryType = UNKNOWN_QUERY;
    protected String selectItem;

    protected Integer varCounter = 1;

    protected Boolean queryCompleted = false;

    /**
     * Creates SPARQL SELECT query.
     * 
     * @param item
     *            - item to be selected
     * @return
     */
    public Query select(final String item) {
        this.queryType = SELECT_QUERY;
        this.selectItem = toVar(item);
        this.query.delete(0, this.query.length());
        this.queryCompleted = false;

        return this;
    }

    /**
     * Creates SPARQL SELECT query. This query will select some label value. <br>
     * Query must be executed with {@link #findLabel(String)} method.
     * 
     * @return
     */
    public Query selectLabel() {
        this.queryType = SELECT_LABEL_QUERY;
        this.selectItem = toVar(SELECT_LABEL);
        this.query.delete(0, this.query.length());
        this.queryCompleted = false;

        return this;
    }

    /**
     * Creates label-check syntax for SPARQL query.
     * 
     * @param item
     *            - <b>variable</b> to be checked.
     * @param label
     *            - label <b>value</b> to be checked
     * @return
     */
    public Query label(final String item, final String label) {
        final String labelVar = nextVar();
        this.query.append(" ");
        this.query.append(toVar(item));
        this.query.append(" rdfs:label ");
        this.query.append(labelVar);
        this.query.append(".");
        this.query.append(" FILTER (");
        this.query.append(labelVar);
        this.query.append(" = \"");
        this.query.append(label);
        this.query.append("\").");

        return this;
    }

    /**
     * Creates connect syntax for SPARQL query.
     * 
     * @param s
     *            - subject. Must be <b>variable</b>.
     * @param p
     *            - predicate. Must be <b>value</b>.
     * @param o
     *            - object. Must be <b>variable</b>.
     * @return
     */
    public Query connect(final String s, final String p, final String o) {
        this.query.append(" ");
        if (isResource(s)) {
            this.query.append(s);
        } else {
            this.query.append(toVar(s));
        }
        this.query.append(" ");
        this.query.append(p);
        this.query.append(" ");
        if (isResource(o)) {
            this.query.append(o);
        } else {
            this.query.append(toVar(o));
        }
        this.query.append(".");

        return this;
    }

    /**
     * Creates SPARQL query syntax for checking type of object.
     * 
     * @param item
     *            - <b>variable</b> to be checked.
     * @param type
     *            - type <b>value</b>.
     * @return
     */
    public Query type(final String item, final String type) {
        this.query.append(" ");
        this.query.append(toVar(item));
        this.query.append(" rdf:type ");
        this.query.append(type);
        this.query.append(".");

        return this;
    }

    /**
     * Creates SPARQL ASK query.
     * 
     * @return
     */
    public Query ask() {
        this.queryType = ASK_QUERY;
        this.query.delete(0, this.query.length());
        this.queryCompleted = false;

        return this;
    }

    /**
     * Executes SPARQL SELECT query. <br>
     * This method should be executed only if query is created via {@link #select(String)}.
     * 
     * @return - Array of strings representing the result of query execution.
     */
    public String[] find() {
        if (this.queryType != SELECT_QUERY) {
            return null;
        } else {
            if (!this.queryCompleted) {
                createSelectQuery();
            }
            this.queryCompleted = true;
            return Ont.quickQueryStr(this.query.toString(), this.selectItem);
        }
    }

    /**
     * Executes SPARQL SELECT label query. <br>
     * This method should be called only if query is created via {@link #selectLabel()}.
     * 
     * @param item
     *            - item who's label should be selected
     * @return - Array of strings representing the result of query execution.
     */
    public String[] findLabel(final String item) {
        if (this.queryType != SELECT_LABEL_QUERY) {
            return null;
        } else {
            if (!this.queryCompleted) {
                createSelectLabelQuery(item);
            }
            this.queryCompleted = true;
            return Ont.quickQueryStr(this.query.toString(), this.selectItem);
        }
    }

    /**
     * Executes SPARQL ASK query. <br>
     * This method should be called only if query is created via {@link #ask()}.
     * 
     * @return - returns existence of graph specified in ASK query.
     */
    public Boolean check() {
        if (this.queryType != ASK_QUERY) {
            return null;
        } else {
            if (!this.queryCompleted) {
                createAskQuery();
            }
            this.queryCompleted = true;
            return Ont.quickAskQuery(this.query.toString());
        }
    }

    /**
     * Creates SPARQL query variable. Function adds {@link #VAR_MARK} perfix to variable name.
     * 
     * @param name
     *            - name of variable.
     * @return
     */
    protected String toVar(final String name) {
        return VAR_MARK + name;
    }

    protected boolean isResource(final String name) {
        return name.contains(":");
    }

    /**
     * Returns next unique variable to be used within SPARQL query.
     * 
     * @return
     */
    protected String nextVar() {
        return VAR_MARK + VAR_NAME + this.varCounter++;
    }

    /**
     * Finalizes SPARQL SELECT query.
     */
    protected void createSelectQuery() {
        this.query.insert(0, "SELECT " + this.selectItem + " WHERE {");
        this.query.append(" }");
    }

    /**
     * Finalizes SPARQL SELECT Label query.
     */
    protected void createSelectLabelQuery(final String item) {
        this.query.insert(0, "SELECT " + this.selectItem + " WHERE {");
        this.query.append(" ");
        if (isResource(item)) {
            this.query.append(item);
        } else {
            this.query.append(toVar(item));
        }
        this.query.append(" rdfs:label ");
        this.query.append(toVar(SELECT_LABEL));
        this.query.append(" }");
    }

    /**
     * Finalizes SPARQL ASK query.
     */
    protected void createAskQuery() {
        this.query.insert(0, "ASK WHERE {");
        this.query.append(" }");
    }

    /**
     * Displays created query. <br>
     * <b>Note: Query is fully formed upon calling {@link #find()} or {@link #check()} method</b>
     * 
     * @return - Created query
     */
    public String showQuery() {
        return this.query.toString();
    }
}
